Segment fragmentation and accumulated deleted-point overhead
The most likely explanation is that the collection has accumulated many small segments and a large number of tombstoned points that the optimizer has not yet consolidated or vacuumed. Qdrant's storage model is segment-based, and every upsert goes to a small mutable segment. Over months, if the optimizer thresholds are set to be lazy, or if the optimizer is constantly behind because of a high write rate, the collection accumulates segments faster than it merges them. Each segment has its own HNSW graph, its own payload indexes, and its own storage overhead. A query fans out to every segment, so the number of segments directly multiplies the per-query cost. Deletes add a second problem: a deleted point is tombstoned rather than removed, so it still occupies space in the segment and still has to be considered during traversal until the segment is vacuumed. If the delete rate is high and the deleted_threshold is not being hit, segments can accumulate a large fraction of tombstones, which wastes traversal work on points that will never be returned.
The mechanism is the interaction between write rate, optimizer thresholds, and query fan-out. The optimizer's job is to keep the segment count and the tombstone ratio bounded, but its thresholds determine how aggressively it acts. If default_segment_number, max_segment_size, indexing_threshold, deleted_threshold, or the optimization thread limit is set too conservatively for the actual write rate, the optimizer falls behind and the collection drifts toward fragmentation. The drift is gradual, which is why the problem appears after months rather than immediately. The symptom is a slow increase in latency that is not explained by data growth alone - the collection may have grown by 20 percent but latency has grown by 3x, because the segment count and tombstone ratio have grown much more. The fix has two parts: identify the accumulated fragmentation, and either allow the optimizer to catch up or force a consolidation by adjusting thresholds.
Segment count: many small segments mean every query fans out more, multiplying per-query cost.
Tombstones: deleted points are not removed until vacuuming; they consume traversal work and storage.
Optimizer lag: if the optimizer cannot keep up with the write rate, segments and tombstones accumulate.
Thresholds: default_segment_number, max_segment_size, indexing_threshold, deleted_threshold, and max_optimization_threads all govern how aggressive the optimizer is.
Query fan-out: each segment has its own HNSW graph, so searching N segments means N graph traversals plus a merge.
Payload indexes: fragmented segments each have their own indexes, multiplying lookup overhead.
Monitor: track segments_count and optimizer status over time to detect drift before it becomes a problem.
The trade-off is between the cost of optimization and the cost of fragmentation. Aggressive optimization consumes CPU and I/O that could be used for queries and ingest, and it causes latency spikes while it runs. Lazy optimization saves resources in the short term but allows fragmentation to accumulate, which eventually costs more in query latency than the optimizer would have cost. The right balance depends on the write rate and the query latency SLO. The common mistake is to leave the optimizer at its defaults and never revisit them as the write rate grows. The second mistake is to assume that a slow query is caused by the vector search itself and start tuning ef and m, when the actual cost is in the fan-out and the tombstones. The third mistake is to force a full re-index as the first response, which is disruptive and expensive and may not be necessary if the optimizer can catch up with threshold adjustments. The alternative to tuning the optimizer is to run the optimizer more aggressively during off-peak hours, which requires scheduling and monitoring. Version note: the optimizer thresholds and their defaults have changed across releases, and some versions interpret the thresholds per-segment rather than per-collection. If the collection was created on an older version and upgraded, the effective threshold behavior may differ from what the original config specified.
Version-dependent: the optimizer config fields and their defaults have changed across releases, and the way optimizer progress is reported has evolved. In some versions the optimizer status is a simple string; in others it includes more detail about what the optimizer is doing. Read the effective config and the actual status at runtime rather than relying on a fixed set of field names or values from an older doc.
Your query latency has slowly increased over months. List the first three metrics you would check to diagnose the cause.
A teammate suggests a full re-index. Explain what the optimizer might be able to do instead and how you would know whether that is sufficient.
You have a collection with a high delete rate and latency is climbing. Describe how tombstones contribute and what optimizer setting you would change.
Your segments_count has grown from 4 to 40 over six months. Explain the implications for query latency and how you would remediate.
Design an optimization schedule for a collection that receives 5k writes per second during business hours and almost no writes at night. What thresholds do you use and how do you switch between them?
You must reduce a collection's segment count from 60 to under 10 without causing query latency spikes during business hours. Describe the plan and the trade-offs.
Derive the relationship between write rate, delete rate, optimizer thresholds, and steady-state segment count. Where does the model predict that the optimizer falls permanently behind?
You are designing a control loop that adjusts optimizer thresholds based on current query load and write rate. Describe the controller, the signals, and how you would validate stability.